package com.wnxy.mallserver.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnxy.mallserver.response.Message;
import com.wnxy.mallserver.response.ResponseResult;
import com.wnxy.mallserver.response.StateCode;
import com.wnxy.mallserver.util.JWTUtil;
import com.wnxy.mallserver.util.TokenEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;


/**
 * 认证过滤器，判断是否登录
 */
@Slf4j
@Component//放进IOC容器中进行管理
public class AuthFilter implements Filter {
    @Resource
    private RedisTemplate<String,Object> redisTemplate;


    /**
     * 过滤器初始化
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    /**
     * 执行过滤
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.debug("后端过滤器执行过滤……");
        //根据请求的URL去判断该请求是否需要登录之后才能访问--URL在request中拿去

        //1.强转成其子类
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String uri = request.getRequestURI();
        log.debug("请求路径：{}",uri);

        //2.筛选需要登录才能访问的ur
        if(requireAuth(uri)){
            //需要登录才能访问
            log.debug("该uri需要登录才能访问");
            //1.从request中获取请求头authorization
            String token = request.getHeader("authorization");
            log.debug(token);

            //2.判断token中是否有值
            // 2.1无值：没登录，终止请求，返回需要去登录的信息；有值：需要进行校验
            if(token == null || token.trim().length() == 0){
                //去登录
                goLogin(response);
                //终止请求
                return;
            }

            //2.2 有值，对进行token返回的类型校验
            TokenEnum verify = JWTUtil.verify(token);
            log.debug("token此时的数据类型"+verify.toString());
            //2.2.1 token是否错误
            if(verify == TokenEnum.TOKEN_BAD){
                //去登录
                goLogin(response);
                //终止请求
                return;
            }
            //2.2.2 token是否过期
            if(verify == TokenEnum.TOKEN_EXPIRE){
                //过期，生成一个新的token，返回给前端，下次请求就携带新的token过来
                //如果redis中也有这个过期的token
                if(redisTemplate.hasKey(token)){
                    //删除redis中的token
                    redisTemplate.delete(token);
                    //从原始的token中拿取uid生成新的token--因为uid存储在token中
                    token = JWTUtil.generateToken(JWTUtil.getuid(token),JWTUtil.getAccount(token));
                    //将新的token放入redis
                    redisTemplate.opsForValue().set(token,"");//这里的set，若redis中有该key值，则自动覆盖该key对应的value
                    //将token放在响应头中传过去--Authorization:权限
                    response.setHeader("authorization", token);
                    //暴露标头，告诉浏览器别过滤这个头，让前端程序能够拿到该标头
                    response.setHeader("Access-Control-Expose-Headers","authorization");
                }else{//token为正常状态
                    goLogin(response);
                    return;
                }

            }

            //代码执行到此处，说明token没问题，或已经更换--将uid统一放在session中。为了避免在各种controller方法中去拿取，直接就能拿来用
            request.getSession().setAttribute("uid",JWTUtil.getuid(token));
        }

        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

    //去登录的方法
    private void goLogin(HttpServletResponse response) throws IOException {
        //没有值，去登录
        ResponseResult<Boolean> result = new ResponseResult<>();
        result.setCode(StateCode.NO_Login);//403:表示没登陆
        result.setMessage(Message.PLEASE_LOGIN);
        result.setData(false);
        //设置响应头
        response.setContentType("application/json;charset=utf-8");
        //将对象转换成json并返回
        String json = new ObjectMapper().writeValueAsString(result);
        //写出数据
        response.getWriter().write(json);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

    /**
     * 验证uri是否需要登录才访问
     * true:需要登录；false：不需要
     */
    private boolean requireAuth(String uri){

        //罗列不需要登录就能访问的uri
        //1.uri要完全对应
        List<String> sames = Arrays.asList(
                "/user/login"
        );
        //2.uri包含关系
        List<String> likes = Arrays.asList(
                "/goods/findGoodsByCid/","/goods/findById/","/images/","/WebSocketHandler/"
        );
        //用于多线程情况下保证数据安全的布尔值
        AtomicBoolean flag = new AtomicBoolean(true);
        //3.循环比较
        sames.forEach(same ->{
            if(uri.equals(same)){
                flag.set(false);
            }
        });
        likes.forEach(like ->{
            if(uri.startsWith(like)){
                flag.set(false);
            }
        });

        return flag.get();//若为setfalse则返回的true
    }
}
